JWT介绍
1. 什么是JWT? (What is JWT?)
想象一下你去一个大型游乐园。在入口处,你买了一张票,工作人员验证后给了你一个防水手环。之后,无论你想玩哪个项目(过山车、旋转木马),你只需要向工作人员展示这个手环,他们看一眼就知道你是合法游客,并且手环上可能还记录了你的“快速通行”权限,而不需要每次都跑回大门口去查验你的购票记录。
在这个比喻中:
- 你:就是客户端(比如浏览器、手机App)。
- 游乐园入口:就是服务器的认证端点(比如登录接口)。
- 你的身份证明(身份证/支付记录):就是你的用户名和密码。
- 防水手环:就是 JWT。
JWT (JSON Web Token),根据其官网(jwt.io )的定义,是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息(以JSON对象的形式)。
这里的关键词是:
- 紧凑 (Compact):由于其体积小,可以通过 URL、POST 参数或者在 HTTP header 中传输,传输速度快。
- 自包含 (Self-contained):Token 本身包含了所有需要验证用户身份的信息(例如用户ID、角色、过期时间等),服务器端无需再去查询数据库。
- 安全 (Secure):信息是经过数字签名的。这意味着你可以验证信息的发送者,并确保信息在传输过程中没有被篡改。
2. 为什么需要JWT? (Why JWT?)
在 JWT 出现之前,最常见的 Web 认证方式是基于 Session-Cookie 的机制。
传统 Session-Cookie 认证流程:
- 客户端用用户名密码登录。
- 服务器验证通过后,创建一个 Session,并将 Session ID 存放在服务器内存或数据库(如 Redis)中。
- 服务器通过
Set-Cookie
响应头将这个 Session ID 发送给客户端。 - 客户端浏览器自动将这个 Cookie 存储起来。
- 之后每次请求,浏览器都会自动带上这个 Cookie。
- 服务器收到请求后,从 Cookie 中拿到 Session ID,再去自己的存储中查找对应的 Session 信息,从而判断用户身份。
这种方式的弊端:
- 状态依赖 (Stateful):服务器需要存储 Session 信息,这增加了服务器的存储开销。
- 扩展性差 (Poor Scalability):如果有多台服务器做负载均衡,需要解决 Session 共享问题。常见的方案是使用“粘性会话”(把用户请求固定到同一台服务器)或引入一个集中的 Session 存储(如 Redis),这都增加了架构的复杂性。
- 跨域认证不便 (CORS Issues):Cookie 存在跨域限制,在微服务架构或前后端分离的应用中,处理跨域认证会很麻烦。
JWT 的出现正是为了解决这些问题: JWT 是无状态的 (Stateless)。服务器端不存储任何 Session 信息。所有信息都编码在 Token 自身,服务器只需要验证 Token 的合法性即可。这使得它在分布式系统、微服务架构中表现得非常出色。
3. JWT 的结构 (The Structure of a JWT)
一个 JWT 实际上是一个由三部分组成的字符串,这三部分之间用点 .
分隔,形式如下:
xxxxx.yyyyy.zzzzz
这三部分分别是:
- Header (头部)
- Payload (载荷)
- Signature (签名)
第一部分:Header (头部)
Header 通常由两部分组成:
typ
(Type): 令牌的类型,固定为 “JWT”。alg
(Algorithm): 使用的签名算法,例如HS256
(HMAC SHA256) 或RS256
(RSA SHA256)。
一个典型的 Header (JSON 格式):
{
"alg": "HS256",
"typ": "JWT"
}
这部分 JSON 会被进行 Base64Url 编码,形成 JWT 的第一部分 xxxxx
。
第二部分:Payload (载荷)
Payload 包含了要传递的数据,这些数据被称为 声明 (Claims)。声明是关于实体(通常是用户)和其他数据的陈述。声明有三种类型:
-
注册声明 (Registered Claims):这些是预定义的一组声明,非强制性,但建议使用,以提供一组有用的、可互操作的声明。
iss
(Issuer): 签发人sub
(Subject): 主题(通常是用户的ID)aud
(Audience): 接收方exp
(Expiration Time): 过期时间(极其重要!)nbf
(Not Before): 生效时间iat
(Issued At): 签发时间jti
(JWT ID): 唯一身份标识,可用于防止重放攻击
-
公共声明 (Public Claims):可以随意定义,但为了避免冲突,应该在 IANA JSON Web Token Registry 中定义,或定义为包含命名空间的 URI。
-
私有声明 (Private Claims):这是在签发方和接收方之间共享使用的声明,既不属于注册声明,也不属于公共声明。这是我们最常用来存放自定义信息的地方。
- 例如:
userId
,username
,role
- 例如:
一个典型的 Payload (JSON 格式):
{
"sub": "1234567890",
"name": "John Doe",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
注意: Payload 部分也是进行 Base64Url 编码,形成 JWT 的第二部分 yyyyy
。这意味着 Payload 中的信息是公开可读的,任何人都可以解码!因此,绝对不要在 Payload 中存放敏感信息,如密码!
第三部分:Signature (签名)
签名部分是 JWT 安全性的核心。它的生成规则如下:
- 将编码后的 Header 和编码后的 Payload 用点
.
连接起来。encodedHeader + "." + encodedPayload
- 使用 Header 中指定的签名算法 (
alg
) 和一个密钥 (Secret) 对这个连接后的字符串进行加密/签名。
例如,如果使用 HS256
算法,签名的伪代码是:
Signature = HMACSHA256(encodedHeader + "." + encodedPayload, secret)
这个 secret
是一个保存在服务器端的密钥,绝对不能泄露给客户端。
签名的作用:
- 防篡改:如果有人修改了 Header 或 Payload,由于他们没有
secret
,无法生成正确的签名。服务器在验证时,会用同样的算法和secret
重新计算签名,如果与收到的签名不匹配,就说明 Token 被篡改了,请求无效。 - 验证来源:确保了 Token 是由你的服务器签发的,而不是伪造的。
4. JWT 的工作流程 (How JWT Works)
- 用户登录:用户使用用户名和密码发起
POST
请求。 - 服务器验证:服务器验证凭据是否正确。
- 生成并签发 Token:如果验证通过,服务器会创建一个包含用户标识(如用户ID、角色等)的 Payload,选择一个签名算法,然后用密钥生成签名,最终组装成一个完整的 JWT。
- 发送 Token 给客户端:服务器将生成的 JWT 返回给客户端。
- 客户端存储 Token:客户端(如浏览器)收到 JWT后,通常会将其存储在
localStorage
、sessionStorage
或HttpOnly
类型的 Cookie 中。 - 在后续请求中携带 Token:客户端在之后向服务器发送的每一个需要认证的请求时,都会在 HTTP Header 的
Authorization
字段中携带这个 JWT,格式通常是Bearer <token>
。Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd...
- 服务器验证 Token:服务器收到请求后,会从
Authorization
头中取出 Token。- 首先,检查签名。用相同的算法和密钥重新计算签名,并与 Token 中的签名进行比对。如果一致,说明 Token 未被篡改且来源可信。
- 然后,检查 Payload 中的声明,如
exp
是否已过期,iss
、aud
是否正确等。
- 处理请求:如果所有验证都通过,服务器就认为该用户是合法的,然后从 Payload 中获取用户信息(如用户ID),执行相应的操作,并返回结果。
5. JWT 的优缺点 (Pros and Cons)
优点 (Pros)
- 无状态与可扩展性:这是最大的优点。服务器不需要保存会话状态,使其易于水平扩展。
- 自包含性:Token 包含了所有必要的用户信息,减少了数据库的查询次数。
- 解耦与跨服务:非常适用于微服务架构,一个认证服务签发 Token,其他业务服务只需验证 Token 即可,服务之间高度解耦。
- 多平台适用:天然支持跨域,对 Web、移动端(iOS/Android)等多种客户端都非常友好。
缺点 (Cons)
- 无法主动失效:一旦一个 JWT 被签发,在它的过期时间(
exp
)到达之前,它就一直是有效的。你无法像 Session 一样在服务器端直接销毁它。如果一个 Token 泄露了,在它过期前,攻击者都可以用它来访问系统。- 解决方案:
- 设置较短的过期时间:比如 15 分钟,并配合“刷新令牌 (Refresh Token)”机制。
- 建立黑名单 (Blocklist):在服务器端建立一个存储已失效 Token 的列表(比如存在 Redis 中)。每次验证 Token 时,先查一下它是否在黑名单里。但这又回到了“状态化”的老路,违背了 JWT 的初衷。
- 解决方案:
- Token 体积:如果 Payload 中存放了太多信息,会导致 Token 变得很大,增加每次请求的流量开销。
- 安全性:签名用的
secret
密钥一旦泄露,整个系统的安全就荡然无存。另外,Payload 是明文的,不应存放敏感数据。
6. 安全注意事项与最佳实践
- 密钥安全:
secret
必须保密,且足够复杂。绝不能硬编码在代码中,应使用环境变量或配置文件管理。 - 使用 HTTPS:始终使用 HTTPS 协议传输 JWT,防止中间人攻击窃取 Token。
- 设置过期时间 (
exp
):必须为 Token 设置一个合理的过期时间,且不宜过长。 - 使用强签名算法:避免使用
none
算法(这是一个历史漏洞,需要服务器端强制校验alg
字段)。推荐使用RS256
(非对称加密)而非HS256
(对称加密),因为RS256
只需要将公钥分发给各个服务进行验证,私钥可以安全地保留在认证服务中,更加安全。 - Payload 不放敏感数据:重申,密码、银行卡号等绝对不能放入 Payload。
- 客户端存储安全:
localStorage
/sessionStorage
:易于使用,但容易受到 XSS (跨站脚本) 攻击。如果网站有 XSS 漏洞,攻击者可以轻易窃取 Token。HttpOnly
Cookie:可以有效防止 XSS 攻击,因为 JS 无法读取HttpOnly
的 Cookie。但需要处理 CSRF (跨站请求伪造) 攻击(例如使用SameSite
属性或 CSRF Token)。
- 处理 Token 失效问题:采用“短生命周期的 Access Token + 长生命周期的 Refresh Token”是目前业界公认的最佳实践,可以在安全性和用户体验之间取得很好的平衡。
总结 (Conclusion)
JWT 是一种强大而灵活的认证和授权机制,特别适合现代的分布式、微服务和无状态应用架构。它通过自包含的、经过签名的 JSON 对象,实现了无状态的身份验证。
然而,它的无状态特性也是一把双刃剑,带来了 Token 无法主动失效的问题。因此,在使用 JWT 时,必须充分理解其工作原理、优缺点,并遵循安全最佳实践,特别是密钥管理、过期时间设置和应对 Token 泄露的策略,才能构建出既高效又安全的系统。
JWT集成
环境准备
- Spring Boot 3.x+
- Java 17+
- Maven 或 Gradle
第1步:添加依赖 (Dependencies)
在你的 pom.xml
文件中,确保有以下依赖:
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Security Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JJWT (Java JWT Library) -->
<!-- 我们需要三个 jjwt 的依赖,分别用于 API、实现和 JSON 解析 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if you prefer -->
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<!-- for testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
第2步:创建 JWT 工具类 (JwtUtils)
这个类是 JWT 功能的核心,负责所有与 Token 相关的操作。
package com.example.security.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
// 从 Token 中提取用户名
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// 从 Token 中提取过期时间
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
// 通用的从 Token 中提取声明的方法
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
// 解析 Token 获取所有声明
private Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(getSigningKey()) // 使用密钥验证
.build()
.parseSignedClaims(token)
.getPayload();
}
// 检查 Token 是否过期
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
// 为指定用户生成 Token
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
// 你可以在这里添加额外的 claims,例如角色
// claims.put("role", userDetails.getAuthorities());
return createToken(claims, userDetails.getUsername());
}
// 创建 Token 的核心逻辑
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.claims(claims) // 设置自定义声明
.subject(subject) // 设置主题(通常是用户名)
.issuedAt(new Date(System.currentTimeMillis())) // 设置签发时间
.expiration(new Date(System.currentTimeMillis() + expiration)) // 设置过期时间
.signWith(getSigningKey()) // 使用密钥和算法签名
.compact();
}
// 验证 Token 是否有效
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
// 检查用户名是否匹配且 Token 未过期
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
// 获取签名密钥
private SecretKey getSigningKey() {
// 使用你的 secret 字符串生成一个安全的密钥
byte[] keyBytes = secret.getBytes();
return Keys.hmacShaKeyFor(keyBytes);
}
}
在你的 application.properties
(或 .yml
) 文件中配置密钥和过期时间:
# 使用一个足够长的、随机的字符串作为密钥!
# 你可以通过在线工具生成一个安全的密钥。
jwt.secret=aVeryLongAndSecureSecretKeyForMySpringBootApplicationThatNobodyShouldKnow
# 过期时间(毫秒),例如:1小时 = 3600000
jwt.expiration=3600000
第3步:创建 JWT 认证过滤器 (JwtAuthFilter)
这个过滤器是 Spring Security 与 JWT 集成的桥梁。它会在每个请求到达受保护资源之前执行,检查是否存在有效的 JWT。
package com.example.security.jwt;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;
// 1. 检查 Header 是否存在或格式是否正确
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response); // 如果不合法,直接跳过此过滤器,交给后续过滤器处理
return;
}
// 2. 提取 JWT
jwt = authHeader.substring(7);
// 3. 从 JWT 中提取用户名
try {
username = jwtUtils.extractUsername(jwt);
} catch (Exception e) {
// Token 解析失败(例如过期、签名错误)
// 可以在这里处理异常,例如返回 401
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// 4. 验证 Token
// 如果用户名存在,并且当前 SecurityContext 中没有认证信息
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 从数据库加载用户信息
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// 验证 Token 是否有效
if (jwtUtils.validateToken(jwt, userDetails)) {
// 如果有效,创建一个认证对象
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null, // 密码我们不需要,因为是 Token 认证
userDetails.getAuthorities()
);
// 设置认证细节
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
// 将认证信息放入 SecurityContext,表示该用户已认证
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
// 5. 继续过滤器链
filterChain.doFilter(request, response);
}
}
第4步:配置 Spring Security (SecurityConfig)
这是最关键的一步,我们需要配置 Spring Security 来:
- 禁用默认的 session 管理,采用无状态(Stateless)策略。
- 配置哪些 URL 是公开的,哪些需要认证。
- 将我们自定义的
JwtAuthFilter
添加到过滤器链中。
package com.example.security.config;
import com.example.security.jwt.JwtAuthFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthFilter jwtAuthFilter;
@Autowired
private UserDetailsService userDetailsService;
// 配置安全过滤器链
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 禁用 CSRF,因为我们使用 JWT,是无状态的
.csrf(AbstractHttpConfigurer::disable)
// 配置 URL 的授权规则
.authorizeHttpRequests(auth -> auth
// 允许对 /api/auth/** 的所有请求,无需认证
.requestMatchers("/api/auth/**").permitAll()
// 其他所有请求都需要认证
.anyRequest().authenticated()
)
// 配置 Session 管理策略为无状态 (STATELESS)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 添加我们自定义的 JWT 过滤器
// 它会在 UsernamePasswordAuthenticationFilter 之前执行
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
// 密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 认证提供者,用于从数据库获取用户信息并进行密码比对
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
// 认证管理器,用于处理登录请求
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
注意: 你需要提供一个 UserDetailsService
的实现,它负责从你的数据库中根据用户名加载用户信息。Spring Security 会用它来验证登录凭据和在 JwtAuthFilter
中加载用户信息。
例如,一个简单的内存 UserDetailsService
实现:
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
在实际项目中,你应该从数据库中查询用户。
第5步:创建认证端点 (AuthController)
最后,我们需要一个 API 接口,让用户可以通过用户名和密码登录,并获取一个 JWT。
package com.example.security.controller;
import com.example.security.dto.AuthRequest;
import com.example.security.dto.AuthResponse;
import com.example.security.jwt.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/login")
public AuthResponse createAuthenticationToken(@RequestBody AuthRequest authRequest) throws Exception {
// 1. 使用 AuthenticationManager 进行用户认证
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
// 2. 如果认证通过,SecurityContextHolder 会保存认证信息
// 我们可以从中获取 UserDetails
final UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 3. 使用 JwtUtils 生成 JWT
final String jwt = jwtUtils.generateToken(userDetails);
// 4. 返回 JWT
return new AuthResponse(jwt);
}
// 创建 DTOs (Data Transfer Objects)
// AuthRequest.java
// record AuthRequest(String username, String password) {}
// AuthResponse.java
// record AuthResponse(String jwt) {}
}
// DTOs
package com.example.security.dto;
public record AuthRequest(String username, String password) {}
package com.example.security.dto;
public record AuthResponse(String jwt) {}
整体流程回顾
-
用户登录:
- 客户端向
/api/auth/login
发送包含用户名和密码的POST
请求。 AuthController
调用AuthenticationManager
来验证凭据。AuthenticationManager
使用DaoAuthenticationProvider
,它会调用你的UserDetailsService
获取用户信息,并用PasswordEncoder
比较密码。- 如果认证成功,
AuthController
调用JwtUtils
生成一个 JWT。 - 服务器将 JWT 返回给客户端。
- 客户端向
-
访问受保护资源:
- 客户端在后续请求的
Authorization
Header 中携带 JWT,格式为Bearer <token>
。 JwtAuthFilter
拦截到请求,从 Header 中提取出 Token。JwtUtils
验证 Token 的签名和有效期。- 如果 Token 有效,过滤器从 Token 中解析出用户名,并使用
UserDetailsService
加载用户完整信息。 - 过滤器创建一个
UsernamePasswordAuthenticationToken
对象,并将其设置到SecurityContextHolder
中。 - 由于
SecurityContext
中有了合法的Authentication
对象,Spring Security 认为该请求已经过认证,允许其访问受保护的 Controller 方法。
- 客户端在后续请求的